import socket
import ustruct
from machine import Pin, I2C
import time
import network


WIFI_SSID = 'Xiaomi_66BB'
WIFI_PASSWORD = '13145211'
# PCA9685 默认地址
PCA9685_ADDRESS = 0x40
# DS3231 的 I2C 地址
DS3231_ADDR = 0x68

def bcd_to_dec(bcd):
    """将BCD格式转换为十进制"""
    return (bcd // 16) * 10 + (bcd % 16)

def dec_to_bcd(dec):
    """将十进制转换为BCD格式"""
    return (dec // 10) * 16 + (dec % 10)

# 寄存器地址
MODE1 = 0x00
MODE2 = 0x01
PRE_SCALE = 0xFE

# LED0 的寄存器基地址
LED0_ON_L = 0x06
LED0_ON_H = 0x07
LED0_OFF_L = 0x08
LED0_OFF_H = 0x09

# 所有LED的ON/OFF寄存器
ALL_LED_ON_L = 0xFA
ALL_LED_ON_H = 0xFB
ALL_LED_OFF_L = 0xFC
ALL_LED_OFF_H = 0xFD

DIGITS = {
    0: [1, 1, 1, 1, 1, 1, 0],
    1: [0, 1, 1, 0, 0, 0, 0],
    2: [1, 1, 0, 1, 1, 0, 1],
    3: [1, 1, 1, 1, 0, 0, 1],
    4: [0, 1, 1, 0, 0, 1, 1],
    5: [1, 0, 1, 1, 0, 1, 1],
    6: [1, 0, 1, 1, 1, 1, 1],
    7: [1, 1, 1, 0, 0, 0, 0],
    8: [1, 1, 1, 1, 1, 1, 1],
    9: [1, 1, 1, 1, 0, 1, 1],
}

class DS3231:
    def __init__(self, i2c):
        self.i2c = i2c

    def set_time(self, year, month, day, hour, minute, second, weekday):
        """设置RTC时间"""
        data = bytearray(7)
        data[0] = dec_to_bcd(second)  # 秒
        data[1] = dec_to_bcd(minute)  # 分
        data[2] = dec_to_bcd(hour)    # 时
        data[3] = dec_to_bcd(weekday) # 星期 (1-7, 1=周日)
        data[4] = dec_to_bcd(day)     # 日
        data[5] = dec_to_bcd(month)   # 月
        data[6] = dec_to_bcd(year - 2000)  # 年 (00-99)
        
        self.i2c.writeto_mem(DS3231_ADDR, 0, data)

    def get_time(self):
        """读取RTC时间"""
        data = self.i2c.readfrom_mem(DS3231_ADDR, 0, 7)
        second = bcd_to_dec(data[0] & 0x7F)
        minute = bcd_to_dec(data[1])
        hour = bcd_to_dec(data[2] & 0x3F)
        weekday = bcd_to_dec(data[3])
        day = bcd_to_dec(data[4])
        month = bcd_to_dec(data[5])
        year = bcd_to_dec(data[6]) + 2000
        
        return (year, month, day, hour, minute, second, weekday)

    def get_temperature(self):
        """读取温度"""
        data = self.i2c.readfrom_mem(DS3231_ADDR, 0x11, 2)
        temp = data[0] + (data[1] >> 6) * 0.25
        return temp


class PCA9685:
    def __init__(self, i2c, address=PCA9685_ADDRESS):
        self.i2c = i2c
        self.address = address
        self.reset()

    def reset(self):
        # 重置设备
        self.write(MODE1, 0x00)

    def write(self, reg, value):
        # 写入寄存器
        self.i2c.writeto_mem(self.address, reg, bytearray([value]))

    def read(self, reg):
        # 读取寄存器
        return self.i2c.readfrom_mem(self.address, reg, 1)[0]

    def set_pwm_freq(self, freq_hz):
        # 设置PWM频率
        prescaleval = 25000000.0  # 25MHz
        prescaleval /= 4096.0  # 12-bit
        prescaleval /= float(freq_hz)
        prescaleval -= 1.0
        prescale = int(prescaleval + 0.5)

        oldmode = self.read(MODE1)
        newmode = (oldmode & 0x7F) | 0x10  # 进入睡眠模式
        self.write(MODE1, newmode)  # 进入睡眠
        self.write(PRE_SCALE, prescale)  # 设置预分频器
        self.write(MODE1, oldmode)
        time.sleep(0.005)
        self.write(MODE1, oldmode | 0x80)  # 唤醒

    def set_pwm(self, channel, on, off):
        # 设置单个PWM通道
        self.write(LED0_ON_L + 4 * channel, on & 0xFF)
        self.write(LED0_ON_H + 4 * channel, on >> 8)
        self.write(LED0_OFF_L + 4 * channel, off & 0xFF)
        self.write(LED0_OFF_H + 4 * channel, off >> 8)

    def set_all_pwm(self, on, off):
        # 设置所有PWM通道
        self.write(ALL_LED_ON_L, on & 0xFF)
        self.write(ALL_LED_ON_H, on >> 8)
        self.write(ALL_LED_OFF_L, off & 0xFF)
        self.write(ALL_LED_OFF_H, off >> 8)

    def set_l(self, channel):
        self.set_pwm(channel, 0, 107)
        time.sleep(0.1)
        self.set_pwm(channel, 0, 0)

    def set_m(self, channel):
        self.set_pwm(channel, 0, 307)
        time.sleep(0.1)
        self.set_pwm(channel, 0, 0)

    def set_r(self, channel):
        self.set_pwm(channel, 0, 507)
        time.sleep(0.1)
        self.set_pwm(channel, 0, 0)

    def fw(self, position):
        offset = position * 7
        self.set_r(0 + offset)
        self.set_r(1 + offset)
        self.set_r(2 + offset)
        self.set_r(3 + offset)
        self.set_l(4 + offset)
        self.set_r(5 + offset)
        self.set_r(6 + offset)

    def set_num_simple_0_1(self, position):
        offset = position * 7
        self.set_r(0 + offset)
        self.set_r(3 + offset)
        self.set_l(4 + offset)
        self.set_r(5 + offset)

    def set_num_simple_1_2(self, position):
        offset = position * 7
        self.set_m(0 + offset)
        self.set_r(2 + offset)
        self.set_m(6 + offset)
        self.set_m(4 + offset)
        self.set_m(3 + offset)

    def set_num_simple_2_3(self, position):
        offset = position * 7
        self.set_m(2 + offset)
        self.set_l(4 + offset)

    def set_num_simple_3_4(self, position):
        offset = position * 7
        self.set_r(0 + offset)
        self.set_r(3 + offset)
        self.set_m(5 + offset)

    def set_num_simple_4_5(self, position):
        offset = position * 7
        self.set_m(0 + offset)
        self.set_r(1 + offset)
        self.set_m(3 + offset)

    def set_num_simple_5_6(self, position):
        offset = position * 7
        self.set_m(4 + offset)

    def set_num_simple_6_7(self, position):
        offset = position * 7
        self.set_m(1 + offset)
        self.set_r(2 + offset)
        self.set_r(3 + offset)
        self.set_l(4 + offset)
        self.set_r(5 + offset)
        self.set_r(6 + offset)
        self.set_m(2 + offset)

    def set_num_simple_7_8(self, position):
        offset = position * 7
        self.set_r(2 + offset)

        self.set_m(5 + offset)
        self.set_m(6 + offset)
        self.set_m(2 + offset)
        self.set_m(4 + offset)
        self.set_m(3 + offset)

    def set_num_simple_8_9(self, position):
        offset = position * 7
        self.set_l(4 + offset)

    def set_num_simple_9_0(self, position):
        offset = position * 7
        self.set_r(2 + offset)
        self.set_r(3 + offset)
        self.set_r(6 + offset)

        self.set_m(2 + offset)
        self.set_m(3 + offset)
        self.set_m(4 + offset)


    def set_num(self, position, num, old_num):
        if old_num is not None and old_num + 1 == num:
            # 简化方式
            if num == 0:
                self.set_num_simple_9_0(position)
            elif num == 1:
                self.set_num_simple_0_1(position)
            elif num == 2:
                self.set_num_simple_1_2(position)
            elif num == 3:
                self.set_num_simple_2_3(position)
            elif num == 4:
                self.set_num_simple_3_4(position)
            elif num == 5:
                self.set_num_simple_4_5(position)
            elif num == 6:
                self.set_num_simple_5_6(position)
            elif num == 7:
                self.set_num_simple_6_7(position)
            elif num == 8:
                self.set_num_simple_7_8(position)
            elif num == 9:
                self.set_num_simple_8_9(position)
        else:
            # 全放倒方式
            self.fw(position)
            # 必须按照顺序来，不要中间的会打架
            for i in [0, 1, 5, 6, 2, 4, 3]:
                if DIGITS.get(num)[i] == 1:
                    self.set_m(position * 7 + i)

def connect_wifi(ssid, password):
    global no_wifi
    sta_if = network.WLAN(network.STA_IF)
    if not sta_if.isconnected():
        print('Connecting to WiFi...')
        sta_if.active(True)
        sta_if.connect(ssid, password)
        count = 0
        while not sta_if.isconnected():
            time.sleep(1)
            count += 1
            if count > 20:
                print("connect wifi failed")
                no_wifi = True
                break
    if not no_wifi:
        print('Network config:', sta_if.ifconfig())


def init():
    for num in range(10):
        pca.set_num(0, num, None)
        pca.set_num(1, num, None)
        pca2.set_num(0, num, None)
        pca2.set_num(1, num, None)
        time.sleep(1)

def get_network_time():
    print("get_network_time...")
    """从阿里云NTP服务器获取时间"""
    # 阿里云NTP服务器地址
    NTP_SERVER = "ntp.aliyun.com"
    NTP_PORT = 123
    
    try:
        # 创建UDP socket
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(5)  # 设置超时时间
        print("settimeout...")
        # 1. 将NTP服务器的域名解析为IP地址
        # getaddrinfo返回一个列表，我们取第一个结果的最后一个元素，即(ip, port)元组
        addr = socket.getaddrinfo(NTP_SERVER, NTP_PORT)[0][-1]
        print(f"Resolved {NTP_SERVER} to {addr}") # 打印解析后的IP，方便调试
        # 构建NTP请求包
        ntp_request = bytearray(48)
        ntp_request[0] = 0x1B  # LI=0, Version=3, Mode=3 (Client)
        print("ntp_request...")
        # 发送请求
        sock.sendto(ntp_request, addr)
        print("sendto...")
        # 接收响应
        data, addr = sock.recvfrom(1024)
        sock.close()
        print("recvfrom...")
        # 解析NTP响应
        if len(data) >= 48:
            # 提取传输时间戳（第40-47字节）
            # NTP时间戳是64位无符号整数，前32位是整数部分，后32位是小数部分
            # 我们只需要整数部分
            transmit_timestamp = ustruct.unpack('!I', data[40:44])[0]
            
            # NTP时间戳从1900年1月1日开始，需要转换为Unix时间戳（从1970年1月1日开始）
            # 它们之间相差 2208988800 秒
            # 同时，由于时区问题，中国是UTC+8，所以需要加上 8 * 3600 秒
            unix_time = transmit_timestamp - 2208988800 + (8 * 3600)
            
            # 转换为本地时间元组
            local_time_tuple = time.localtime(unix_time)
            
            # 从元组中提取时、分、秒
            year = local_time_tuple[0]
            month = local_time_tuple[1]
            day = local_time_tuple[2]
            hour = local_time_tuple[3]
            minute = local_time_tuple[4]
            second = local_time_tuple[5]
            # time.localtime() 返回的星期是 0-6 (周一=0, 周日=6)
            # DS3231 通常需要 1-7 (周一=1, 周日=7)
            weekday = local_time_tuple[6] + 1

            print(f"Network time: {year}-{month}-{day} {hour:02d}:{minute:02d}:{second:02d}, weekday: {weekday}")
            
            return year, month, day, hour, minute, second, weekday
        else:
            print("len(data) < 48", len(data))
            return None, None, None, None, None, None, None
            
    except Exception as e:
        print(f"error: {e}")
        return None, None, None, None, None, None, None


if __name__ == '__main__':
    # 初始化I2C
    i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
    # 创建PCA9685实例
    pca = PCA9685(i2c)
    pca2 = PCA9685(i2c, 0x41)
    # 设置PWM频率为50Hz (适合舵机控制)
    pca.set_pwm_freq(50)
    pca2.set_pwm_freq(50)
    # 初始化8
    init()

    no_wifi = False
    # WiFi 配置
    connect_wifi(WIFI_SSID, WIFI_PASSWORD)
    
    # 创建DS3231实例
    ds3231 = DS3231(i2c)
    # 设置时间
    if not no_wifi:
        year, month, day, hour, minute, second, weekday = get_network_time()
        if year is not None:
            print(f"set time: {year}-{month}-{day} {hour:02d}:{minute:02d}:{second:02d}, weekday: {weekday}")
            ds3231.set_time(year, month, day, hour, minute, second, weekday)

    # 开始循环获取时间并设置显示
    old_h1 = None
    old_h2 = None
    old_m1 = None
    old_m2 = None
    while True:
        year, month, day, hour, minute, second, weekday = ds3231.get_time()
        print(f"get time: {year}-{month}-{day} {hour:02d}:{minute:02d}:{second:02d}, weekday: {weekday}")
        h1 = hour //10
        h2 = hour % 10
        m1 = minute // 10
        m2 = minute % 10
        if old_h1 != h1:
            pca2.set_num(1, h1, old_h1)
            old_h1 = h1
        if old_h2 != h2:
            pca2.set_num(0, h2, old_h2)
            old_h2 = h2
        if old_m1 != m1:
            pca.set_num(1, m1, old_m1)
            old_m1 = m1
        if old_m2 != m2:
            pca.set_num(0, m2, old_m2)
            old_m2 = m2
        time.sleep(1)
